package com.example.icampusevent;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.app.ActionBar;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.Menu;
import android.widget.Toast;

import com.example.icampusevent.MapRoute.RouteListener;
import com.google.android.maps.GeoPoint;
import com.google.android.maps.MapActivity;
import com.google.android.maps.MapController;
import com.google.android.maps.MapView;
import com.google.android.maps.Overlay;
import com.google.android.maps.OverlayItem;
import com.google.android.maps.Projection;
import android.widget.ImageView;


public class CampusMapActivity extends MapActivity implements LocationListener {
	
	private LocationManager locationManager = null;
	private String locationProvider = null;
	private Projection projection = null;
	private MapView campusMapView = null;
	private List<Overlay> mapOverlays = null;
	private MapTag tags = null;
	
	private double desLan;
	private double desLng;
	
	Handler mHandler = null;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_campus_map);
        
        ActionBar actionBar = getActionBar();
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
        
        // Disable the built-in main action bar
        getActionBar().setDisplayShowHomeEnabled(false);
        getActionBar().setDisplayShowTitleEnabled(false);
        
        // Add marker to map
        campusMapView = (MapView)findViewById(R.id.campus_map_view);
        mapOverlays = campusMapView.getOverlays();
        Drawable drawable = this.getResources().getDrawable(R.drawable.map_marker);
        tags = new MapTag(drawable, this);
        
        // Draw route
        drawRoute(setMyLocation(), setDestination());
    }
    
    private void drawRoute(GeoPoint srcGeoPoint, GeoPoint desGeoPoint) {
    	DrawRouteTask backgroundTask = new DrawRouteTask();
        GeoPoint points[] = {srcGeoPoint, desGeoPoint};
        backgroundTask.execute(points);
        
        MapController controller = campusMapView.getController();
        controller.setCenter(srcGeoPoint);
        controller.animateTo(srcGeoPoint);
    }
    
    private GeoPoint setMyLocation() {
        Location location = getCurrentLocation();
        if(location == null) {
        	toast("Error: cannot get current location.");
        	return null;
        }
        GeoPoint srcGeoPoint = new GeoPoint((int)(location.getLatitude() * 1E6), 
        		(int)(location.getLongitude() * 1E6));
        OverlayItem srcItem = new OverlayItem(srcGeoPoint, "Hello!", "Have Fun!");
        tags.addTag(srcItem);
        // mapOverlays.add(tags);
        
        return srcGeoPoint;
    }
    
    private GeoPoint setDestination() {
    	Intent intent = getIntent();
        this.desLan = intent.getDoubleExtra("LAN", 0);
        this.desLng = intent.getDoubleExtra("LNG", 0);
        
    	GeoPoint desGeoPoint = new GeoPoint((int)(desLan * 1E6), 
        		(int)(desLng * 1E6));
    	OverlayItem destItem = new OverlayItem(desGeoPoint, "Yeah!", "Destination");
    	tags.addTag(destItem);
    	mapOverlays.add(tags);
    	
    	return desGeoPoint;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_map, menu);
        return true;
    }

	@Override
	protected boolean isRouteDisplayed() {
		return true;
	}
	
	private Location getCurrentLocation() {
		locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
		Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
	    if(location != null)
	    	return location;
	    else 
	    	return null;
	}

	public void onLocationChanged(Location location) {
		// TODO Auto-generated method stub
		
	}

	public void onProviderDisabled(String provider) {
		// TODO Auto-generated method stub
		
	}

	public void onProviderEnabled(String provider) {
		// TODO Auto-generated method stub
		
	}

	public void onStatusChanged(String provider, int status, Bundle extras) {
		// TODO Auto-generated method stub
		
	}
	
	public interface Parser {
        public Route parse();
	}
	
	public class XMLParser {
        // names of the XML tags
        protected static final String MARKERS = "markers";
        protected static final String MARKER = "marker";

        protected URL feedUrl;

        protected XMLParser(final String feedUrl) {
                try {
                        this.feedUrl = new URL(feedUrl);
                } catch (MalformedURLException e) {
                        //Log.e(e.getMessage(), "XML parser - " + feedUrl);
                }
        }

        protected InputStream getInputStream() {
                try {
                        return feedUrl.openConnection().getInputStream();
                } catch (IOException e) {
                        //Log.e(e.getMessage(), "XML parser - " + feedUrl);
                        return null;
                }
        }
	}
	
	public class Segment {
        /** Points in this segment. **/
        private GeoPoint start;
        /** Turn instruction to reach next segment. **/
        private String instruction;
        /** Length of segment. **/
        private int length;
        /** Distance covered. **/
        private double distance;

        /**
         * Create an empty segment.
         */

        public Segment() {
        }


        /**
         * Set the turn instruction.
         * @param turn Turn instruction string.
         */

        public void setInstruction(final String turn) {
                this.instruction = turn;
        }

        /**
         * Get the turn instruction to reach next segment.
         * @return a String of the turn instruction.
         */

        public String getInstruction() {
                return instruction;
        }

        /**
         * Add a point to this segment.
         * @param point GeoPoint to add.
         */

        public void setPoint(final GeoPoint point) {
                start = point;
        }

        /** Get the starting point of this 
         * segment.
         * @return a GeoPoint
         */

        public GeoPoint startPoint() {
                return start;
        }

        /** Creates a segment which is a copy of this one.
         * @return a Segment that is a copy of this one.
         */

        public Segment copy() {
                final Segment copy = new Segment();
                copy.start = start;
                copy.instruction = instruction;
                copy.length = length;
                copy.distance = distance;
                return copy;
        }

        /**
         * @param length the length to set
         */
        public void setLength(final int length) {
                this.length = length;
        }

        /**
         * @return the length
         */
        public int getLength() {
                return length;
        }

        /**
         * @param distance the distance to set
         */
        public void setDistance(double distance) {
                this.distance = distance;
        }

        /**
         * @return the distance
         */
        public double getDistance() {
                return distance;
        }

	}
	
	public class Route {
        private String name;
        private final List<GeoPoint> points;
        private List<Segment> segments;
        private String copyright;
        private String warning;
        private String country;
        private int length;
        private String polyline;

        public Route() {
                points = new ArrayList<GeoPoint>();
                segments = new ArrayList<Segment>();
        }

        public void addPoint(final GeoPoint p) {
                points.add(p);
        }

        public void addPoints(final List<GeoPoint> points) {
                this.points.addAll(points);
        }

        public List<GeoPoint> getPoints() {
                return points;
        }

        public void addSegment(final Segment s) {
                segments.add(s);
        }

        public List<Segment> getSegments() {
                return segments;
        }

        /**
         * @param name the name to set
         */
        public void setName(final String name) {
                this.name = name;
        }

        /**
         * @return the name
         */
        public String getName() {
                return name;
        }

        /**
         * @param copyright the copyright to set
         */
        public void setCopyright(String copyright) {
                this.copyright = copyright;
        }

        /**
         * @return the copyright
         */
        public String getCopyright() {
                return copyright;
        }

        /**
         * @param warning the warning to set
         */
        public void setWarning(String warning) {
                this.warning = warning;
        }

        /**
         * @return the warning
         */
        public String getWarning() {
                return warning;
        }

        /**
         * @param country the country to set
         */
        public void setCountry(String country) {
                this.country = country;
        }

        /**
         * @return the country
         */
        public String getCountry() {
                return country;
        }

        /**
         * @param length the length to set
         */
        public void setLength(int length) {
                this.length = length;
        }

        /**
         * @return the length
         */
        public int getLength() {
                return length;
        }


        /**
         * @param polyline the polyline to set
         */
        public void setPolyline(String polyline) {
                this.polyline = polyline;
        }

        /**
         * @return the polyline
         */
        public String getPolyline() {
                return polyline;
        }

	}
	
	public class GoogleParser extends XMLParser implements Parser {
        /** Distance covered. **/
        private int distance;

        public GoogleParser(String feedUrl) {
                super(feedUrl);
        }

        /**
         * Parses a url pointing to a Google JSON object to a Route object.
         * @return a Route object based on the JSON object.
         */

        public Route parse() {
                // turn the stream into a string
                final String result = convertStreamToString(this.getInputStream());
                //Create an empty route
                final Route route = new Route();
                //Create an empty segment
                final Segment segment = new Segment();
                try {
                        //Tranform the string into a json object
                        final JSONObject json = new JSONObject(result);
                        //Get the route object
                        final JSONObject jsonRoute = json.getJSONArray("routes").getJSONObject(0);
                        //Get the leg, only one leg as we don't support waypoints
                        final JSONObject leg = jsonRoute.getJSONArray("legs").getJSONObject(0);
                        //Get the steps for this leg
                        final JSONArray steps = leg.getJSONArray("steps");
                        //Number of steps for use in for loop
                        final int numSteps = steps.length();
                        //Set the name of this route using the start & end addresses
                        route.setName(leg.getString("start_address") + " to " + leg.getString("end_address"));
                        //Get google's copyright notice (tos requirement)
                        route.setCopyright(jsonRoute.getString("copyrights"));
                        //Get the total length of the route.
                        route.setLength(leg.getJSONObject("distance").getInt("value"));
                        //Get any warnings provided (tos requirement)
                        if (!jsonRoute.getJSONArray("warnings").isNull(0)) {
                                route.setWarning(jsonRoute.getJSONArray("warnings").getString(0));
                        }
                        /* Loop through the steps, creating a segment for each one and
                         * decoding any polylines found as we go to add to the route object's
                         * map array. Using an explicit for loop because it is faster!
                         */
                        for (int i = 0; i < numSteps; i++) {
                                //Get the individual step
                                final JSONObject step = steps.getJSONObject(i);
                                //Get the start position for this step and set it on the segment
                                final JSONObject start = step.getJSONObject("start_location");
                                final GeoPoint position = new GeoPoint((int) (start.getDouble("lat")*1E6), 
                                        (int) (start.getDouble("lng")*1E6));
                                segment.setPoint(position);
                                //Set the length of this segment in metres
                                final int length = step.getJSONObject("distance").getInt("value");
                                distance += length;
                                segment.setLength(length);
                                segment.setDistance(distance/1000);
                                //Strip html from google directions and set as turn instruction
                                segment.setInstruction(step.getString("html_instructions").replaceAll("<(.*?)*>", ""));
                                //Retrieve & decode this segment's polyline and add it to the route.
                                route.addPoints(decodePolyLine(step.getJSONObject("polyline").getString("points")));
                                //Push a copy of the segment to the route
                                route.addSegment(segment.copy());
                        }
                } catch (JSONException e) {
                        Log.e(e.getMessage(), "Google JSON Parser - " + feedUrl);
                }
                return route;
        }

        /**
         * Convert an inputstream to a string.
         * @param input inputstream to convert.
         * @return a String of the inputstream.
         */

        private String convertStreamToString(final InputStream input) {
        final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
        final StringBuilder sBuf = new StringBuilder();

        String line = null;
        try {
            while ((line = reader.readLine()) != null) {
                sBuf.append(line);
            }
        } catch (IOException e) {
                Log.e(e.getMessage(), "Google parser, stream2string");
        } finally {
            try {
                input.close();
            } catch (IOException e) {
                Log.e(e.getMessage(), "Google parser, stream2string");
            }
        }
        return sBuf.toString();
    }

        /**
         * Decode a polyline string into a list of GeoPoints.
         * @param poly polyline encoded string to decode.
         * @return the list of GeoPoints represented by this polystring.
         */

        private List<GeoPoint> decodePolyLine(final String poly) {
                int len = poly.length();
                int index = 0;
                List<GeoPoint> decoded = new ArrayList<GeoPoint>();
                int lat = 0;
                int lng = 0;

                while (index < len) {
                int b;
                int shift = 0;
                int result = 0;
                do {
                        b = poly.charAt(index++) - 63;
                        result |= (b & 0x1f) << shift;
                        shift += 5;
                } while (b >= 0x20);
                int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
                lat += dlat;

                shift = 0;
                result = 0;
                do {
                        b = poly.charAt(index++) - 63;
                        result |= (b & 0x1f) << shift;
                        shift += 5;
                } while (b >= 0x20);
                        int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
                        lng += dlng;

                decoded.add(new GeoPoint(
                        (int) (lat*1E6 / 1E5), (int) (lng*1E6 / 1E5)));
                }

                return decoded;
                }
	}
	
	
	class RouteOverlay extends Overlay {
        /** GeoPoints representing this routePoints. **/
        private final List<GeoPoint> routePoints;
        /** Colour to paint routePoints. **/
        private int colour;
        /** Alpha setting for route overlay. **/
        private static final int ALPHA = 120;
        /** Stroke width. **/
        private static final float STROKE = 4.5f;
        /** Route path. **/
        private final Path path;
        /** Point to draw with. **/
        private final Point p;
        /** Paint for path. **/
        private final Paint paint;


        /**
         * Public constructor.
         * 
         * @param route Route object representing the route.
         * @param defaultColour default colour to draw route in.
         */

        public RouteOverlay(final Route route, final int defaultColour) {
                super();
                routePoints = route.getPoints();
                colour = defaultColour;
                path = new Path();
                p = new Point();
                paint = new Paint();
        }

        @Override
        public final void draw(final Canvas c, final MapView mv,
                        final boolean shadow) {
                super.draw(c, mv, shadow);

                paint.setColor(colour);
                paint.setAlpha(ALPHA);
                paint.setAntiAlias(true);
                paint.setStrokeWidth(STROKE);
                paint.setStyle(Paint.Style.STROKE);
                paint.setPathEffect(new DashPathEffect(new float[] {20,10}, 0));

                redrawPath(mv);
                c.drawPath(path, paint);
        }

        /**
         * Set the colour to draw this route's overlay with.
         * 
         * @param c  Int representing colour.
         */
        public final void setColour(final int c) {
                colour = c;
        }

        /**
         * Clear the route overlay.
         */
        public final void clear() {
                routePoints.clear();
        }

        /**
         * Recalculate the path accounting for changes to
         * the projection and routePoints.
         * @param mv MapView the path is drawn to.
         */

        private void redrawPath(final MapView mv) {
                final Projection prj = mv.getProjection();
                path.rewind();
                final Iterator<GeoPoint> it = routePoints.iterator();
                prj.toPixels(it.next(), p);
                path.moveTo(p.x, p.y);
                while (it.hasNext()) {
                        prj.toPixels(it.next(), p);
                        path.lineTo(p.x, p.y);
                }
                path.setLastPoint(p.x, p.y);
        }

	}
	
	private Route directions(final GeoPoint start, final GeoPoint dest) {
	    Parser parser;
	    String jsonURL = "http://maps.googleapis.com/maps/api/directions/json?origin=";
	    final StringBuffer sBuf = new StringBuffer(jsonURL);
	    sBuf.append(start.getLatitudeE6()/1E6);
	    sBuf.append(',');
	    sBuf.append(start.getLongitudeE6()/1E6);
	    sBuf.append("&destination=");
	    sBuf.append(dest.getLatitudeE6()/1E6);
	    sBuf.append(',');
	    sBuf.append(dest.getLongitudeE6()/1E6);
	    sBuf.append("&sensor=false&avoid=highways&mode=bicycling");
	    Log.d("xxx", sBuf.toString());
	    parser = new GoogleParser(sBuf.toString());
	    Route r =  parser.parse();
	    return r;
	}
	
	
	private class DrawRouteTask extends AsyncTask<GeoPoint, Void, Void> {
	    @Override
	    protected Void doInBackground(GeoPoint... points) {
	    	Route route = directions(points[0], points[1]);
	    	RouteOverlay routeOverlay = new RouteOverlay(route, Color.RED);
	    	campusMapView.getOverlays().add(routeOverlay);
			return null;
	    }

	    @Override
	    protected void onPostExecute(Void nothing) {
	    }
	}
	
	
	private void drawPath(GeoPoint src,GeoPoint dest, int color, MapView mMapView01) 
	{ 
		// connect to map web service 
		StringBuilder urlString = new StringBuilder();
		/*
		urlString.append("http://maps.google.com/maps?f=d&hl=en"); 
		urlString.append("&saddr=");//from 
		urlString.append( Double.toString((double)src.getLatitudeE6()/1.0E6 )); 
		urlString.append(","); 
		urlString.append( Double.toString((double)src.getLongitudeE6()/1.0E6 )); 
		urlString.append("&daddr=");//to 
		urlString.append( Double.toString((double)dest.getLatitudeE6()/1.0E6 )); 
		urlString.append(","); 
		urlString.append( Double.toString((double)dest.getLongitudeE6()/1.0E6 ));
		urlString.append("&ie=UTF8&0&om=0&output=kml"); 
		*/
		urlString.append("https://maps.google.com/maps?f=d&hl=en#sadd=34.031568, -118,283126$daddr=34.018501,-118.283401&ie=UTF8&0&output=kml");
		Log.d("xxx","URL="+urlString.toString());
		
		// get the kml (XML) doc. And parse it to get the coordinates(direction route). 
		Document doc = null; 
		HttpURLConnection urlConnection= null; 
		URL url = null; 
		try 
		{ 
			url = new URL(urlString.toString()); 
			urlConnection=(HttpURLConnection)url.openConnection(); 
			urlConnection.setRequestMethod("GET"); 
			urlConnection.setDoOutput(true); 
			urlConnection.setDoInput(true); 
			urlConnection.connect(); 
		
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 
			DocumentBuilder db = dbf.newDocumentBuilder(); 
			doc = db.parse(urlConnection.getInputStream()); 
			Log.d("xxx", "kml="+ doc.toString());
		
			if(doc.getElementsByTagName("GeometryCollection").getLength()>0) 
			{ 
				//String path = doc.getElementsByTagName("GeometryCollection").item(0).getFirstChild().getFirstChild().getNodeName(); 
				String path = doc.getElementsByTagName("GeometryCollection").item(0).getFirstChild().getFirstChild().getFirstChild().getNodeValue() ; 
				Log.d("xxx","path="+ path); 
				String [] pairs = path.split(" "); 
				String[] lngLat = pairs[0].split(","); // lngLat[0]=longitude lngLat[1]=latitude lngLat[2]=height 
				// src 
				GeoPoint startGP = new GeoPoint((int)(Double.parseDouble(lngLat[1])*1E6),(int)(Double.parseDouble(lngLat[0])*1E6)); 
				mMapView01.getOverlays().add(new MapRouteOverlay(startGP,startGP,1)); 
				GeoPoint gp1; 
				GeoPoint gp2 = startGP; 
				for(int i=1;i<pairs.length;i++) // the last one would be crash 
				{ 
					lngLat = pairs[i].split(","); 
					gp1 = gp2; 
					// watch out! For GeoPoint, first:latitude, second:longitude 
					gp2 = new GeoPoint((int)(Double.parseDouble(lngLat[1])*1E6),(int)(Double.parseDouble(lngLat[0])*1E6)); 
					mMapView01.getOverlays().add(new MapRouteOverlay(gp1,gp2,2,color)); 
					Log.d("xxx","pair:" + pairs[i]); 
				} 
				mMapView01.getOverlays().add(new MapRouteOverlay(dest,dest, 3)); // use the default color 
			} 
		} 
		catch (MalformedURLException e) 
		{ 
			e.printStackTrace(); 
		} 
		catch (IOException e) 
		{ 
			e.printStackTrace(); 
		} 
		catch (ParserConfigurationException e) 
		{ 
			e.printStackTrace(); 
		} 
		catch (SAXException e) 
		{ 
			e.printStackTrace(); 
		} 
	}
	
	public void toast(String s) {
		Context context = getApplicationContext();
		CharSequence text = s;
		int duration = Toast.LENGTH_LONG;
		Toast toast = Toast.makeText(context, text, duration);
		toast.show();
	}
}
